﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WCell.Constants.Spells;
using WCell.RealmServer.Spells.Auras.Handlers;
using WCell.Util.Data;

namespace WCell.RealmServer.Spells
{
	/// <summary>
	/// Aura-related information of a Spell
	/// </summary>
	public partial class Spell
	{
		public delegate bool AuraIdEvaluator(Spell spell);

		/// <summary>
		/// Whether this Spell is an Aura
		/// </summary>
		public bool IsAura;

		/// <summary>
		/// AuraUID, the same for all Auras that may not stack
		/// </summary>
		public uint AuraUID;

		public bool HasPeriodicAuraEffects;

		public bool HasNonPeriodicAuraEffects;

		/// <summary>
		/// AuraFlags to be applied to all Auras resulting from this Spell
		/// </summary>
		public AuraFlags DefaultAuraFlags;

		/// <summary>
		/// General Amplitude for Spells that represent Auras (can only have one for the time being)
		/// </summary>
		public int AuraAmplitude;

		/// <summary>
		/// whether this Spell is an AreaAura
		/// </summary>
		public bool IsAreaAura;

		/// <summary>
		/// Modal Auras cannot be updated, but must be replaced
		/// </summary>
		public bool IsModalAura;

		/// <summary>
		/// General Amplitude for Spells that represent AreaAuras (can only have one per spell)
		/// </summary>
		public int AreaAuraAmplitude;

		/// <summary>
		/// All effects that belong to an Aura
		/// </summary>
		public SpellEffect[] AuraEffects;

		/// <summary>
		/// All effects that belong to an AreaAura
		/// </summary>
		public SpellEffect[] AreaAuraEffects;

		/// <summary>
		/// whether the Aura's effects should be applied once for each of its Applications
		/// </summary>
		public bool CanStack;

		/// <summary>
		/// The amount of initial Aura-Applications
		/// </summary>
		public int Applications;

		public bool IsPureAura;

		public bool IsPureBuff;

		public bool IsPureDebuff;

		public bool IsProc;

		public bool IsVehicle;

		/// <summary>
		/// whether this Spell applies the death effect
		/// </summary>
		public bool IsGhost;

		/// <summary>
		/// whether this spell applies makes the targets fly
		/// </summary>
		public bool HasFlyEffect;

		/// <summary>
		/// Does this Spell apply a Mount-Aura?
		/// </summary>
		public bool IsMount
		{
			get { return Mechanic == SpellMechanic.Mounted; }
		}

		/// <summary>
		/// Does this Spell apply a Flying-Mount Aura?
		/// </summary>
		public bool IsFlyingMount;

		/// <summary>
		/// 
		/// </summary>
		public AuraCasterGroup AuraCasterGroup;


		private void InitAura()
		{
			IsAura = HasEffectWith(effect =>
			{
				if (effect.AuraType != AuraType.None)
				{
					return true;
				}
				return false;
			});

			if (SpellId == SpellId.TameAdultPlainstrider)
			{
				ToString();
			}

			if (IsAura)
			{
				ForeachEffect(effect =>
				{
					if (effect.IsAuraEffect)
					{
						HasNonPeriodicAuraEffects = HasNonPeriodicAuraEffects || !effect.IsPeriodic;
						HasPeriodicAuraEffects = HasPeriodicAuraEffects || effect.IsPeriodic;
					}
				});
			}

			IsModalAura =
				AttributesExB.Has(SpellAttributesExB.AutoRepeat) ||
				HasEffectWith(effect => effect.IsExlusive);

			if (IsAura)
			{
				HasManaShield = HasEffectWith(effect => effect.AuraType == AuraType.ManaShield);
			}

			var auraEffects = GetEffectsWith(effect => effect.AuraEffectHandlerCreator != null);
			if (auraEffects != null)
			{
				AuraEffects = auraEffects.ToArray();
			}

			var areaAuraEffects = GetEffectsWith(effect => effect.IsAreaAuraEffect);

			if (areaAuraEffects != null)
			{
				AreaAuraEffects = areaAuraEffects.ToArray();
			}

			IsAreaAura = AreaAuraEffects != null;

			IsPureAura = !IsDamageSpell && !HasEffectWith(effect => effect.EffectType != SpellEffectType.ApplyAura ||
				effect.EffectType != SpellEffectType.ApplyAuraToMaster || effect.EffectType != SpellEffectType.ApplyStatAura ||
				effect.EffectType != SpellEffectType.ApplyStatAuraPercent);

			IsPureBuff = IsPureAura && HasBeneficialEffects && !HasHarmfulEffects;

			IsPureDebuff = IsPureAura && HasHarmfulEffects && !HasBeneficialEffects;

			IsVehicle = HasEffectWith(effect => effect.AuraType == AuraType.Vehicle);

			// procs and stacking
			if (MaxStackCount > 0)
			{
				// each application will be re-applied on each Aura-tick, but starts with 1
				CanStack = true;
				Applications = 1;
			}
			else
			{
				// Applications will be used up by procs
				MaxStackCount = ProcCharges;
				Applications = ProcCharges;
			}

			IsGhost = HasEffectWith(effect => effect.AuraType == AuraType.Ghost);


			HasFlyEffect = HasEffectWith(effect => effect.AuraType == AuraType.Fly);

			IsFlyingMount = IsMount &&
							HasEffectWith(effect => effect.AuraType == AuraType.ModSpeedMountedFlight);

			// procs
			if (ProcTriggerFlags != ProcTriggerFlags.None)
			{
				ProcTriggerEffects = Effects.Where(effect => effect.IsProc).ToArray();
				if (ProcTriggerEffects.Length == 0)
				{
					// no proc-specific effects -> all effects are triggered on proc
					ProcTriggerEffects = null;
				}
				if (ProcTriggerFlags == (ProcTriggerFlags.MeleeAttackSelf | ProcTriggerFlags.SpellCast))
				{
					// we don't want any SpellCast to trigger on that
					ProcTriggerFlags = ProcTriggerFlags.MeleeAttackSelf;
				}
				IsProc = ProcTriggerEffects != null;
			}

			if (AuraUID == 0)
			{
				CreateAuraUID();
			}
		}

		#region AuraUID Evaluation
		private void CreateAuraUID()
		{
			var count = (uint)AuraIdEvaluators.Count;
			//for (var i = count-1; i >= 0; i--)
			for (var i = 0u; i < count; i++)
			{
				var eval = AuraIdEvaluators[(int)i];
				if (eval(this))
				{
					AuraUID = i + 1;
					break;
				}
			}

			if (AuraUID == 0)
			{
				// by default the uid is the id of all spells in one line
				// and single spells get single unique ids
				if (Line != null)
				{
					AuraUID = (uint)Line.LineId;
				}
				else
				{
					AuraUID = count + ++lastAuraUid;
				}
			}
		}

		private static uint lastAuraUid;

		/// <summary>
		/// Every Aura that is evaluated to true is only stackable with Auras that are also evaluated to true by the same evaluator.
		/// Spells that are not covered by any evaluator have no restrictions.
		/// </summary>
		public static readonly List<AuraIdEvaluator> AuraIdEvaluators = new List<AuraIdEvaluator>();

		internal static void RegisterAuraUIDEvaluators()
		{
			AddEvaluator(IsTransform);
			AddEvaluator(IsStealth);
		}

		public static bool IsTransform(Spell spell)
		{
			return spell.HasEffectWith(effect =>
									   effect.AuraType == AuraType.Transform ||
									   effect.AuraType == AuraType.ModShapeshift);
		}

		public static bool IsStealth(Spell spell)
		{
			return spell.HasEffectWith(effect =>
									   effect.AuraType == AuraType.ModStealth);
		}

		public static void AddEvaluator(AuraIdEvaluator eval)
		{
			AuraIdEvaluators.Add(eval);
		}

		/// <summary>
		/// Defines a set of Auras that are mutually exclusive
		/// </summary>
		public static void AddAuraGroup(IEnumerable<Spell> auras)
		{
			var uid = ++lastAuraUid;
			foreach (var spell in auras)
			{
				spell.AuraUID = uid;
			}
		}

		/// <summary>
		/// Defines a set of Auras that are mutually exclusive
		/// </summary>
		public static void AddAuraGroup(params SpellId[] auras)
		{
			var uid = ++lastAuraUid;
			foreach (var id in auras)
			{
				var spell = SpellHandler.Get(id);
				if (spell == null)
				{
					throw new ArgumentException("Invalid SpellId: " + id);
				}
				spell.AuraUID = uid;
			}
		}

		/// <summary>
		/// Defines a set of Auras that are mutually exclusive
		/// </summary>
		public static void AddAuraGroup(params SpellLineId[] auraLines)
		{
			var uid = ++lastAuraUid;
			foreach (var lineId in auraLines)
			{
				var line = SpellLines.GetLine(lineId);
				foreach (var spell in line)
				{
					spell.AuraUID = uid;
				}
			}
		}
		#endregion

		public bool IsStrongerThan(Spell spell)
		{
			return Rank > spell.Rank;
		}
	}
}
